package com.study.springbootsecurity.service.impl;

import cn.hutool.core.codec.Base64;
import com.google.zxing.WriterException;
import com.study.springbootsecurity.domain.dto.QRCodeDTO;
import com.study.springbootsecurity.enums.QrCodeStatuesChangeEventType;
import com.study.springbootsecurity.enums.QrCodeStatus;
import com.study.springbootsecurity.exception.BusinessException;
import com.study.springbootsecurity.service.QrCodeService;
import com.study.springbootsecurity.util.QrCodeUtil;
import com.study.springbootsecurity.util.RedisCache;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Description: TODO
 * @author: Huangjianyun
 * @date: 2025-06-20 13:15
 */
@Slf4j
@Service
public class QrCodeServiceImpl implements QrCodeService {

    private static final Long QRCODE_TTL_MILLI_SECONDS = 60 * 60 * 1000L;
    private static final String QR_CODE_REDIS_KEY_PREFIX = "qrcode:uuid:";
    @Autowired
    private RedisCache redisCache;

    @Resource
    private StateMachinePersister<QrCodeStatus, QrCodeStatuesChangeEventType,String> qrCodeRedisPersister;
    @Resource
    private StateMachine<QrCodeStatus, QrCodeStatuesChangeEventType> qrcodeStateMachine;

    @Override
    public QRCodeDTO generateQRCode() {
        // 生成唯一uuid
        String content = UUID.randomUUID().toString();
        QRCodeDTO qrCodeDTO = new QRCodeDTO();
        String base64QrCode =  createQrCodeBase64(content);

        if (StringUtils.isBlank(base64QrCode)) {
            log.error("UUID为{}二维码生成失败",content);
            throw new BusinessException("UUID为"+content+"生成二维码失败");
        }

        long curTime = System.currentTimeMillis();
        // qrCodeExpire： 有效期; QRCODE_KEY_TTL: 二维码还剩多少毫秒过期 2min过期 = 2 * 60 *1000
        long qrCodeExpire = curTime + QRCODE_TTL_MILLI_SECONDS;
        // 设置二维码初始状态待扫描状态：1
        QrCodeStatus status = QrCodeStatus.WAITING_SCAN ;
        qrCodeDTO.setBase64QrCode(base64QrCode);
        qrCodeDTO.setCreateAt(curTime);
        qrCodeDTO.setExpireDate(qrCodeExpire);
        qrCodeDTO.setStatus(status);
        qrCodeDTO.setUuid(content);

        String qrKey = "qrcode:uuid:" + content;
        Map<String,Object> map = new HashMap<>();
        map.put("status",status);
        map.put("curTime",curTime);
        map.put("expireAt",qrCodeExpire);
        redisCache.set(qrKey,map,QRCODE_TTL_MILLI_SECONDS, TimeUnit.MILLISECONDS);
        // 生成环境这行注释掉
        log.info("已为 UUID: {} 生成二维码，过期时间为: {} (TTL {} 秒)",content,qrCodeExpire,redisCache.getTTL(qrKey));
        return qrCodeDTO;
    }

    private String createQrCodeBase64(String content) {
        // 生成二维码
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            QrCodeUtil.createCodeToOutputStream(content,null,outputStream);
        } catch (IOException | WriterException e) {
            log.error("二维码生成失败",e);
        }
        // 输出流转换字节数据后 转换base64
        byte[] qrcodeByteArray = outputStream.toByteArray();
        return Base64.encode(qrcodeByteArray);
    }
    //    QR_CODE_SCANNED_EVENT (1,"二维码扫码事件"),
    // 二维码扫码事件
    @Override
    public void scannedQRCode(String content){
        // redis获取二维码状态数据
        String key = QR_CODE_REDIS_KEY_PREFIX + content;
        QRCodeDTO qrCodeDTO = redisCache.getCacheObject(key);
        log.info("线程名称：{},尝试手机客户端扫码，二维码uuid：{}" ,Thread.currentThread().getName() , content);

        if (ObjectUtils.isEmpty(qrCodeDTO)) {
            throw new BusinessException("redis存储二维码状态数据不存在");
        }
        // 二维码扫码失败
        if(!sendEvent(QrCodeStatuesChangeEventType.QR_CODE_SCANNED_EVENT,qrCodeDTO)) {
            log.info("线程名称：{},手机客户端扫码，二维码uuid：{}" ,Thread.currentThread().getName() , content);
            throw new BusinessException("二维码扫码事件失败");
        }

    }
    // USER_CONFIRMED_EVENT：二维码扫码后,手机客户端用户确认按钮，确认授权事件
    @Override
    public void userConfirmed(String content){
        // redis获取二维码状态数据
        String key = QR_CODE_REDIS_KEY_PREFIX + content;
        QRCodeDTO qrCodeDTO = redisCache.getCacheObject(key);
        log.info("线程名称：{},尝试手机扫码后点击登录按钮,确认授权，二维码uuid：{}" ,Thread.currentThread().getName() , content);

        if (ObjectUtils.isEmpty(qrCodeDTO)) {
            throw new BusinessException("redis存储二维码状态数据不存在");
        }
        // 二维码扫码失败
        if(!sendEvent(QrCodeStatuesChangeEventType.USER_CONFIRMED_EVENT,qrCodeDTO)) {
            log.info("线程名称：{},手机扫码后点击登录按钮,确认授权，二维码uuid：{}" ,Thread.currentThread().getName() , content);
            throw new BusinessException("手机扫码后点击登录按钮,确认授权事件失败");
        }
    }
    //    USER_CANCELED_EVENT (3,"二维码扫码后,用户取消授权事件"),

    @Override
    public void userCanceled(String content) {
        // redis 获取二维码数据
        String key = QR_CODE_REDIS_KEY_PREFIX + content;
        QRCodeDTO qrCodeDTO = redisCache.getCacheObject(key);
        if (ObjectUtils.isEmpty(qrCodeDTO)) {
            throw new BusinessException("redis存储二维码数据不存在,二维码可能已过期或不存在二维码");
        }
        if (!sendEvent(QrCodeStatuesChangeEventType.USER_CANCELED_EVENT,qrCodeDTO)) {
            throw new BusinessException("手机扫码后点击取消授权按钮,取消授权事件失败");
        }
    }
    //    EXPIRED_EVENT (4,"过期事件");
    @Override
    public void expired(String content) {
// redis 获取二维码数据
        String key = QR_CODE_REDIS_KEY_PREFIX + content;
        QRCodeDTO qrCodeDTO = redisCache.getCacheObject(key);
        if (ObjectUtils.isEmpty(qrCodeDTO)) {
            throw new BusinessException("redis存储二维码数据不存在,二维码可能已过期或不存在二维码");
        }
        if (!sendEvent(QrCodeStatuesChangeEventType.EXPIRED_EVENT,qrCodeDTO)) {
            throw new BusinessException("二维码创建完成未扫码,二维码过期,状态变更过期触发过期事件失败");
        }
    }

    /**
     * 发送订单状态转换事件
     * synchronized修饰保证这个方法是线程安全的
     *
     * @param changeEvent 变更状态触发事件
     * @param qrCodeDTO 验证码状态数据
     * @return
     */
    private synchronized boolean sendEvent(QrCodeStatuesChangeEventType changeEvent, QRCodeDTO qrCodeDTO) {
        boolean result = false;
        try {
            //启动状态机
            qrcodeStateMachine.start();
            qrCodeRedisPersister.restore(qrcodeStateMachine,String.valueOf(qrCodeDTO.getUuid()));
            Message message = MessageBuilder.withPayload(changeEvent).setHeader("qrCodeDTO", qrCodeDTO).build();
            // 状态机发送消息
            result = qrcodeStateMachine.sendEvent(message);
            //持久化状态机状态
            qrCodeRedisPersister.persist(qrcodeStateMachine, String.valueOf(qrCodeDTO.getUuid()));
        } catch (Exception e) {
            log.error("验证码操作失败:{}", e);
            // throw new BusinessException(e.getMessage());
        } finally {
            qrcodeStateMachine.stop();
        }
        return result;
    }


}
